#pragma once

namespace VirtualPedal
{
using namespace System;
using namespace System::Reflection;
using namespace System::Collections::Generic;
using namespace System::Collections::ObjectModel;

ref class PedalBase;
ref class TweakablePropertyAttribute;

public ref class TweakablePropertyInfo
{
	System::Reflection::PropertyInfo^ m_PropertyInfo;
public:
	TweakablePropertyInfo(System::Reflection::PropertyInfo^ propInfo, TweakablePropertyAttribute^ attr);
	property float MaxValue;
	property float MinValue;
	property float DefaultValue;
	property String^ Name
	{
		String^ get() { return m_PropertyInfo->Name; }
	}
	void SetValue(PedalBase^ owner, float value);
	float GetValue(PedalBase^ owner);
};


public ref class TweakablePropertyAttribute : public System::Attribute
{
public:
	property float MaxValue;
	property float MinValue;
	property float DefaultValue;
	property bool Log;
	
	TweakablePropertyAttribute(float minValue, float maxValue, float defaultValue, bool bLog)
	{
		MaxValue = maxValue;
		MinValue = minValue;
		DefaultValue = defaultValue;
		Log = bLog;
	}
};

public ref class PedalBase abstract
{
protected:
	UInt32 m_NumChannels;
	UInt32 m_SamplingRate;

	PedalBase()
	{
		IsEnabled = true;
	}

public:
	property UInt32 NumChannels {
		UInt32 get() { return m_NumChannels; }
		void set(UInt32 value) {
			if(value == m_NumChannels)
				return;
			m_NumChannels = value;
			OnNumChannelsChanged();
		}
	}

	property String^ Name { 
		virtual String^ get() abstract;
	}

	property UInt32 SamplingRate
	{
		UInt32 get() { return m_SamplingRate; }
		void set(UInt32 value) { 
			if(m_SamplingRate == value)
				return;
			m_SamplingRate = value;
			OnSamplingRateChanged();
		}
	}

	property bool IsEnabled;
	
	property UInt32 NumBufferedSamples;

	virtual void Process(float* pSrc, float* pDst, UINT numSamples) abstract;

	System::Collections::ObjectModel::ReadOnlyCollection<TweakablePropertyInfo^>^ GetTweakableProperties()
	{
		List<TweakablePropertyInfo^>^ properties = gcnew List<TweakablePropertyInfo^>();
		System::Type^ type = this->GetType();
		for each(System::Reflection::PropertyInfo^ propInfo in type->GetProperties())
		{
			auto attrs = propInfo->GetCustomAttributes(TweakablePropertyAttribute::typeid, true);
			if(attrs != nullptr && attrs->Length > 0)
			{
				properties->Add(gcnew TweakablePropertyInfo(propInfo, dynamic_cast<TweakablePropertyAttribute^>(attrs[0])));
			}
		}
		return gcnew System::Collections::ObjectModel::ReadOnlyCollection<TweakablePropertyInfo^>(properties);
	}

protected:
	virtual void OnNumChannelsChanged() { }
	virtual void OnSamplingRateChanged() { }
internal:
	virtual void OnTweakablePropertyChanged() { }
};

public ref class ShelvingPedal : public PedalBase
{
public:
	enum class EShelvingType
	{
		BaseShelf,
		TrebleShelf,
	};

protected:
	float a1;
	float a2;
	float b0;
	float b1;
	float b2;

	float G;
	float Fc;
	float Q;
	EShelvingType m_Type;

	array<array<float>^>^ HistoricX;
	array<array<float>^>^ HistoricY;

	int HistoryPointer;

public:

	property String^ Name { 
		virtual String^ get() override { return gcnew String(L"Shelving"); }
	}

	[TweakablePropertyAttribute(-10, 10, 4, false)]
	property float Gain {
		float get() { return G; } 
		void set(float value) { G = value; }
	}

	[TweakablePropertyAttribute(20, 3000, 300, false)]
	property float CenterFrequency {
		float get() { return Fc; }
		void set(float value) { Fc = value; }
	}

	[TweakablePropertyAttribute(1, 10, 3, false)]
	property float SlopeTerm {
		float get() { return Q; }
		void set(float value) { Q = value; }
	}

	property EShelvingType ShelvingType {
		EShelvingType get() { return m_Type; }
		void set(EShelvingType value) { m_Type = value; }
	}

	ShelvingPedal()
	{
		Gain = 4;
		CenterFrequency = 300;
		SlopeTerm = 3;
		SamplingRate = 44100;
		ShelvingType = EShelvingType::BaseShelf;

		HistoryPointer = 0;

		OnTweakablePropertyChanged();
	}

	virtual void Process(float* pSrc, float* pDst, UINT numSamples) override
	{
		UINT idx = 0;
		for(UINT s=0; s<numSamples; s++)
		{
			for(UINT c=0; c<m_NumChannels; c++)
			{
				float x0 = HistoricX[c][HistoryPointer] = pSrc[idx];
				float x2 = HistoricX[c][(HistoryPointer + 1) % 3];
				float x1 = HistoricX[c][(HistoryPointer + 2) % 3];
				float y2 = HistoricY[c][(HistoryPointer + 1) % 3];
				float y1 = HistoricY[c][(HistoryPointer + 2) % 3];
				float y0 = (b2 * x2 + b1 * x1 + b0 * x0 - a2 * y2 - a1 * y1) / 1.0f;	//a0 = 1
				pDst[idx] = HistoricY[c][HistoryPointer] = y0;
				idx++;
			}
			HistoryPointer = (HistoryPointer + 1) % 3;
		}
	}

protected:

	virtual void OnNumChannelsChanged() override
	{
		HistoricX = gcnew array<array<float>^>(m_NumChannels);
		HistoricY = gcnew array<array<float>^>(m_NumChannels);
		for(UInt32 i=0; i<m_NumChannels; i++)
		{
			HistoricX[i] = gcnew array<float>(3);
			HistoricY[i] = gcnew array<float>(3);
		}
	}

	virtual void OnSamplingRateChanged() override
	{
		OnTweakablePropertyChanged();
	}

internal:

	virtual void OnTweakablePropertyChanged() override
	{
		float K = tanf(((float)Math::PI * Fc) / SamplingRate);
		float KK = K * K;
		float V0 = powf(10, G / 20);
		float root2 = 1 / Q;

		if(V0 < 1)
		{
			V0 = 1 / V0;
		}

		//Base boost
		if(G > 0 && ShelvingType == EShelvingType::BaseShelf)
		{
			b0 = (1 + sqrtf(V0) * root2 * K + V0 * KK) / (1 + root2 * K + KK);
			b1 = (2 * (V0 * KK - 1)) / (1 + root2 * K + KK);
			b2 = (1 - sqrtf(V0) * root2 * K + V0 * KK) / (1 + root2 * K + KK);
			a1 = (2 * (KK - 1)) / (1 + root2 * K + KK);
			a2 = (1 - root2 * K + KK) / (1 + root2 * K + KK);
		}
		//Base cut
		else if(G < 0 && ShelvingType == EShelvingType::BaseShelf)
		{
			b0 = (1 + root2 * K + KK) / (1 + root2 * sqrtf(V0) * K + V0 * KK);
			b1 = (2 * (KK - 1)) / (1 + root2 * sqrtf(V0) * K + V0 * KK);
			b2 = (1 - root2 * K + KK) / (1 + root2 * sqrtf(V0) * K + V0 * KK);
			a1 = (2 * (V0 * KK - 1)) / (1 + root2 * sqrtf(V0) * K + V0 * KK);
			a2 = (1 - root2 * sqrtf(V0) * K + V0 * KK) / (1 + root2 * sqrtf(V0) * K + V0 * KK);
		}
		//Treble boost
		else if(G > 0 && ShelvingType == EShelvingType::TrebleShelf)
		{
			b0 = (V0 + root2 * sqrtf(V0) * K + KK) / (1 + root2 * K + KK);
			b1 = (2 * (KK - V0)) / (1 + root2 * K + KK);
			b2 = (V0 - root2 * sqrtf(V0) * K + KK) / (1 + root2 * K + KK);
			a1 = (2 * (KK - 1)) / (1 + root2 * K + KK);
			a2 = (1 - root2 * K + KK) / (1 + root2 * K + KK);
		}
		//Treble cut
		else if(G < 0 && ShelvingType == EShelvingType::TrebleShelf)
		{
			b0 = (1 + root2 * K + KK) / (V0 + root2 * sqrtf(V0) * K + KK);
			b1 = (2 * (KK - 1)) / (V0 + root2 * sqrtf(V0) * K + KK);
			b2 = (1 - root2 * K + KK) / (V0 + root2 * sqrtf(V0) * K + KK);
			a1 = (2 * (KK / V0 - 1)) / (1 + root2 / sqrtf(V0) * K + KK / V0);
			a2 = (1 - root2 / sqrtf(V0) * K + KK / V0) / (1 + root2 / sqrtf(V0) * K + KK / V0);
		}
		else 
		{
			b0 = V0;
			b1 = 0;
			b2 = 0;
			a1 = 0;
			a2 = 0;
		}
	}
};

public ref class DistortionPedal : public PedalBase
{
public:

	property String^ Name { 
		virtual String^ get() override { return gcnew String(L"Distortion"); }
	}

	[TweakablePropertyAttribute(1.0f, 50.0f, 10.0f, false)]
	property float Gain;

	[TweakablePropertyAttribute(0.0f, 1.0f, 1.0f, false)]
	property float Mix;

	DistortionPedal()
	{
		Gain = 10.0f;
		Mix = 1.0f;
	}

	virtual void Process(float* pSrc, float* pDst, UINT numSamples) override
	{
		for(UINT i=0; i<numSamples; i++)
		{
			//pDst[i] = pSrc[i];
			pDst[i] = Fuzzexp_v1(pSrc[i]);
		}
	}
private:

	template <typename T> 
	T sign(T t)  
	{ 
		if( t == 0 ) 
			return T(0); 
		else 
			return (t < 0) ? T(-1) : T(1);
	}

	template <typename T>
	T lerp(T a, T b, float t)
	{
		return (a * (1.0f - t)) + b * t;
	}

	float Fuzzexp(float x)
	{
		return lerp(x, sign(x) * (1 - exp(-Gain * x * sign(x))), Mix);
	}

	float Fuzzexp_v1(float x)
	{
		return lerp(x, sign(x) * (1 - exp(-Gain * abs(x))), Mix);
	}

	float Fuzzexp_v2(float x)
	{
		float thita = asin(min(1.0f, max(-1.0f, x)));
		float sum = 0.0f;
		for(int i=1; i<8; i+=2)
		{
			sum += 2 * sinf(i * thita) / (i * 3.14159f);
		}
		return lerp(x, sum, Mix);
	}
};

public ref class OverdrivePedal : public PedalBase
{
public:
	virtual void Process(float* pSrc, float* pDst, UINT numSamples) override
	{
		for(UINT i=0; i<numSamples; i++)
		{
			//pDst[i] = pSrc[i];
			pDst[i] = Symclip(pSrc[i]);
		}
	}

	property String^ Name { 
		virtual String^ get() override { return gcnew String(L"Overdrive"); }
	}

private:

	float Symclip(float x)
	{
		const float Threshold = 1.0f / 3;
		float y = 0.0f;

		x = min(1, max(-1, x));

		float absx = abs(x);

		if(absx < Threshold)
		{
			y = 2 * x;
		}
		else if(absx < 2 * Threshold)
		{
			if(x > 0)
			{
				y = (3 - powf(2 - x * 3, 2)) / 3;
			}
			else
			{
				y = -(3 - powf(2 - absx * 3, 2)) / 3;
			}
		}
		else
		{
			if(x > 0)
				y = 1.0f;
			else
				y = -1.0f;
		}
		return y;
	}
};

}